package com.sh.data.engine.domain.api.service.impl;

import cn.hutool.core.convert.Convert;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sh.data.engine.common.enumDefinition.DSType;
import com.sh.data.engine.common.enumDefinition.DataApiCallStatusEnum;
import com.sh.data.engine.common.util.DateUtil;
import com.sh.data.engine.common.util.DateUtil.DATE_PATTERN;
import com.sh.data.engine.common.util.ServletUtils;
import com.sh.data.engine.domain.api.model.domain.DataApiCallRecordDomain;
import com.sh.data.engine.domain.api.model.domain.DataApiErrDistributionSummaryDomain;
import com.sh.data.engine.domain.api.model.domain.rest.DataApiCallSummaryDomain;
import com.sh.data.engine.domain.api.model.param.DataApiAddRecordParam;
import com.sh.data.engine.domain.api.model.param.DataApiCallRecordParam;
import com.sh.data.engine.domain.api.service.DataApiCallRecordService;
import com.sh.data.engine.domain.api.service.DataApiService;
import com.sh.data.engine.domain.common.service.DirectoryService;
import com.sh.data.engine.domain.integration.datasource.service.DataSourceService;
import com.sh.data.engine.domain.util.ConvertUtil;
import com.sh.data.engine.repository.dao.api.DataApiCallRecordMapper;
import com.sh.data.engine.repository.dao.api.entity.DataApiCallRecordEntity;
import com.sh.data.engine.repository.dao.api.entity.DataApiEntity;
import com.sh.data.engine.repository.dao.common.entity.DirectoryEntity;
import com.sh.data.engine.repository.dao.integration.datasource.entity.DataSourceEntity;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author : fuchen
 * @date : 2022/1/25 14:10
 */
@Service
public class DataApiCallRecordServiceImpl
    extends ServiceImpl<DataApiCallRecordMapper, DataApiCallRecordEntity>
    implements DataApiCallRecordService {

    @Value("${data.engine.metadataDSType:PostgreSQL}")
    private String metadataDSType;

    @Autowired
    private DataApiService dataApiService;

    @Autowired
    private DataSourceService dataSourceService;

    @Autowired
    private DirectoryService directoryService;

    @Override
    public List<DataApiErrDistributionSummaryDomain> getErrDistributionSummary(
        Long projectId, String id) {

        LambdaQueryWrapper<DataApiCallRecordEntity> eq =
            new LambdaQueryWrapper<DataApiCallRecordEntity>()
                .eq(DataApiCallRecordEntity::getProjectId, projectId);
        if (Objects.nonNull(id)) {
            eq.eq(DataApiCallRecordEntity::getUserId, id);
        }
        QueryWrapper<DataApiCallRecordEntity> queryWrapper =
            new QueryWrapper<DataApiCallRecordEntity>()
                .select("count(1) as value , call_status as callStatus")
                .eq("project_id", projectId)
                .groupBy("call_status");
        List<Map<String, Object>> maps = listMaps(queryWrapper);
        DataApiCallStatusEnum[] values = DataApiCallStatusEnum.values();

        List<DataApiErrDistributionSummaryDomain> res = Lists.newArrayList();
        for (DataApiCallStatusEnum dataApiCallStatusEnum : values) {
            long value = 0;
            if (dataApiCallStatusEnum.getStatus().equals(DataApiCallStatusEnum.SUCCESS.getStatus())) {
                continue;
            }
            String name = dataApiCallStatusEnum.getName();
            Integer status = dataApiCallStatusEnum.getStatus();
            DataApiErrDistributionSummaryDomain domain1 = new DataApiErrDistributionSummaryDomain();
            domain1.setName(name);
            domain1.setCallStatus(status);
            domain1.setValue(value);
            for (Map<String, Object> domain : maps) {
                Object callStatus1 = domain.get("callstatus");
                if (Objects.isNull(callStatus1)) {
                    callStatus1 = domain.get("CALLSTATUS");
                }
                if (Objects.isNull(callStatus1)) {
                    callStatus1 = domain.get("callStatus");
                }
                if (Objects.nonNull(callStatus1)) {
                    String callStatus = callStatus1.toString();
                    Object value2 = domain.get("value");
                    if (value2 == null) {
                        value2 = domain.get("VALUE");
                    }
                    if (Objects.nonNull(value2)) {
                        Long value1 = Long.valueOf(value2.toString());
                        if (Integer.valueOf(callStatus).equals(status)) {
                            domain1.setValue(value1);
                        }
                    }
                }
            }
            res.add(domain1);
        }
        return res;
    }

    @Override
    public DataApiCallRecordDomain addRecord(DataApiAddRecordParam param) {

        Date begin = new Date();
        DataApiCallRecordEntity entity = new DataApiCallRecordEntity();
        entity.setProjectId(param.getProjectId());
        entity.setUserId(param.getUserId());
        entity.setDataApiId(param.getDataApiId());
        entity.setCallType(param.getCallType());
        entity.setCallBeginTime(begin);
        entity.setRequestAddress(param.getRequestAddress());
        entity.setIsProcessed(0);
        this.save(entity);

        DataApiCallRecordDomain domain =
            ConvertUtil.copyProperties(entity, DataApiCallRecordDomain.class);
        domain.setCallBeginTime(begin);
        return domain;
    }

    @Override
    public void updateRecordStatus(DataApiCallRecordDomain recordDomain, Integer status) {
        Date end = new Date();
        DataApiCallRecordEntity entity = this.getById(recordDomain.getId());
        entity.setId(recordDomain.getId());
        entity.setCallStatus(status);
        entity.setCallEndTime(end);
        entity.setIsProcessed(0);
        this.updateById(entity);
    }

    @Override
    public void updateRecordBatch(List<DataApiCallRecordDomain> recordDomains) {
        List<DataApiCallRecordEntity> dataApiCallRecordEntities =
            Convert.toList(DataApiCallRecordEntity.class, recordDomains);

        this.updateBatchById(dataApiCallRecordEntities);
    }

    public static List<Long> findIntersection(List<Long> list1, List<Long> list2, List<Long> list3) {
        Set<Long> set1 = new HashSet<>(list1);
        Set<Long> set2 = new HashSet<>(list2);
        set1.retainAll(set2);
        return new ArrayList<>(set1);
    }

    @Override
    public IPage<DataApiCallSummaryDomain> listDataApiRecord(DataApiCallRecordParam convert) {

        LambdaQueryWrapper<DataApiCallRecordEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();

        Long projectId = ServletUtils.getProjectId();
        List<Long> nameApiIds = null;

        if (Objects.nonNull(convert.getDatasourceId())) {
            List<Long> collect = com.google.common.collect.Lists.newArrayList();
            Long dataSourceId = convert.getDatasourceId();
            List<DataApiEntity> list = dataApiService.list(
                new LambdaQueryWrapper<>(DataApiEntity.class).eq(DataApiEntity::getDatasourceId,
                    dataSourceId));
            if (CollectionUtils.isNotEmpty(list)) {
                collect = list.stream().map(e -> e.getId()).collect(Collectors.toList());
                if (CollectionUtils.isEmpty(collect)) {
                    collect.add(0L);
                }
            } else {
                collect.add(0L);
            }
            lambdaQueryWrapper.in(DataApiCallRecordEntity::getDataApiId, collect);
        }

        if (Objects.nonNull(convert.getGroupId())) {
            Long groupId = convert.getGroupId();
            List<DataApiEntity> list = dataApiService.list(
                new LambdaQueryWrapper<>(DataApiEntity.class).eq(DataApiEntity::getApiGroupId,
                    groupId));
            List<Long> collect = Lists.newArrayList();
            if (CollectionUtils.isNotEmpty(list)) {
                collect = list.stream().map(e -> e.getId()).collect(Collectors.toList());
                if (CollectionUtils.isEmpty(collect)) {
                    collect.add(0L);
                }
            } else {
                collect.add(0L);
            }
            lambdaQueryWrapper.in(DataApiCallRecordEntity::getDataApiId, collect);
        }

        if (StringUtils.isNotEmpty(convert.getApiName())) {

            nameApiIds = Lists.newArrayList();
            List<DataApiEntity> list = dataApiService.list(
                new LambdaQueryWrapper<>(DataApiEntity.class).like(DataApiEntity::getApiName,
                    convert.getApiName()));
            List<Long> collect = com.google.common.collect.Lists.newArrayList();
            if (CollectionUtils.isNotEmpty(list)) {
                collect = list.stream().map(e -> e.getId()).collect(Collectors.toList());
                nameApiIds.addAll(collect);
            }
        }

        if (Objects.nonNull(convert.getType()) && Objects.nonNull(convert.getUsedTime())) {
            String metadataDSType = this.metadataDSType;
            if (metadataDSType.equalsIgnoreCase(DSType.MySQL.name()) || metadataDSType.equalsIgnoreCase(
                DSType.DM.name())) {
                if (convert.getType().equals(1)) {
                    lambdaQueryWrapper.apply(
                        "TIMESTAMPDIFF(MICROSECOND, call_begin_time, call_end_time) / 1000 <= {0}",
                        convert.getUsedTime());

                } else if (convert.getType().equals(0)) {
                    lambdaQueryWrapper.apply(
                        "TIMESTAMPDIFF(MICROSECOND, call_begin_time, call_end_time) / 1000 >= {0}",
                        convert.getUsedTime());

                }
            } else {
                if (convert.getType().equals(1)) {
                    lambdaQueryWrapper.apply(
                        "EXTRACT(EPOCH FROM (call_end_time - call_begin_time)) * 1000 <= {0}",
                        convert.getUsedTime());
                } else if (convert.getType().equals(0)) {
                    lambdaQueryWrapper.apply(
                        "EXTRACT(EPOCH FROM (call_end_time - call_begin_time)) * 1000 >= {0}",
                        convert.getUsedTime());
                }
            }
        }

        if (StringUtils.isNotEmpty(convert.getEndTime()) && StringUtils.isNotEmpty(
            convert.getStartTime())) {
            Date getStart = DateUtil.parse(convert.getStartTime(), DATE_PATTERN.yyyyMMddHHmmss);

            Date getEnd = DateUtil.parse(convert.getEndTime(), DATE_PATTERN.yyyyMMddHHmmss);
            lambdaQueryWrapper.ge(DataApiCallRecordEntity::getCallBeginTime, getStart)
                .le(DataApiCallRecordEntity::getCallEndTime, getEnd);
        }

        String orderField = convert.getOrderField();
        if (StringUtils.isNotEmpty(orderField)) {
            if (orderField.equals("callBeginTime")) {
                lambdaQueryWrapper.last("order by " + " call_begin_time " + convert.getOrder());
            }
            if (orderField.equals("callEndTime")) {
                lambdaQueryWrapper.last("order by " + " call_end_time " + convert.getOrder());
            }
            if (orderField.equals("responseTime")) {
                lambdaQueryWrapper.last(
                    "order by " + "  (COALESCE(call_end_time,call_begin_time) - call_begin_time) "
                        + convert.getOrder());
            }
        } else {
            lambdaQueryWrapper.orderByDesc(DataApiCallRecordEntity::getCreateTime);
        }
        lambdaQueryWrapper.isNull(DataApiCallRecordEntity::getIgnored);
        lambdaQueryWrapper.eq(DataApiCallRecordEntity::getProjectId, projectId);
        IPage<DataApiCallSummaryDomain> r1 = page(
            new Page<>(convert.getPageNum(), convert.getPageSize()), lambdaQueryWrapper).convert(
            e -> Convert.convert(DataApiCallSummaryDomain.class, e));

        Map<Long, DataApiEntity> dataApiEntityMap = Maps.newHashMap();
        List<DataApiCallSummaryDomain> records = r1.getRecords();
        List<Long> collect = records.stream().map(e -> e.getDataApiId()).collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(collect)) {
            List<DataApiEntity> list = dataApiService.list(
                new LambdaQueryWrapper<>(DataApiEntity.class).in(DataApiEntity::getId, collect));
            dataApiEntityMap = list.stream()
                .collect(Collectors.toMap(e -> e.getId(), e -> e));
        }

        for (DataApiCallSummaryDomain domain : records) {
            DataApiEntity dataApiEntity = dataApiEntityMap.get(domain.getDataApiId());
            domain.setApiName(dataApiEntity.getApiName());

            DataSourceEntity byId = dataSourceService.getById(dataApiEntity.getDatasourceId());
            if (Objects.nonNull(byId)) {
                domain.setDatasourceName(byId.getDsName());
            }

            if (Objects.nonNull(dataApiEntity.getApiGroupId())) {
                if (dataApiEntity.getApiGroupId().equals(0L)) {
                    domain.setApiGroupName("全部分组");
                    domain.setGroupId(0L);
                } else {
                    DirectoryEntity directory = directoryService.getById(dataApiEntity.getApiGroupId());
                    if (Objects.isNull(directory)) {
                        domain.setApiGroupName("无");
                        domain.setGroupId(dataApiEntity.getApiGroupId());
                    } else {
                        domain.setApiGroupName(directory.getDirName());
                    }
                }
            }

            if (Objects.nonNull(domain.getCallEndTime())) {
                domain.setResponseTime(
                    domain.getCallEndTime().getTime() - domain.getCallBeginTime().getTime());
            }
        }
        return r1;
    }

    @Override
    public void batchIgnored(List<Long> collect) {
        UpdateWrapper<DataApiCallRecordEntity> wrapper = Wrappers.update();
        wrapper
            .lambda()
            .in(DataApiCallRecordEntity::getId, collect);
        DataApiCallRecordEntity dataApiCallRecordEntity = new DataApiCallRecordEntity();
        dataApiCallRecordEntity.setIgnored(1);
        this.update(dataApiCallRecordEntity, wrapper);
    }

    @Override
    public void keepHalfMonthRecord(Date date) {

        LambdaQueryWrapper<DataApiCallRecordEntity> lt =
            new LambdaQueryWrapper<DataApiCallRecordEntity>().lt(DataApiCallRecordEntity::getCreateTime,
                date);
        this.remove(lt);

    }
}
